﻿using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Indentation;
using NFox.Pycad;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace NFox.Pyeditor.Runtime
{
    public class CodeEditor : TextEditor
    {

        public Module Module { get; private set; }

        public static dynamic Cstyle { get; set; }

        public MarkerService MarkerService { get; private set; }

        CompletionWindow _cw;
        OverloadInsightWindow _iw;
        public CodeEditor(Module m) : this()
        {
            Module = m;
            Text = File.ReadAllText(m.File.FullName);
        }

        public CodeEditor()
        {

            SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("Python");
            Options.ConvertTabsToSpaces = true;
            Options.HighlightCurrentLine = true;
            ShowLineNumbers = true;
            Options.InheritWordWrapIndentation = false;

            //断点管理器，待定
            TextArea.IndentationStrategy = new DefaultIndentationStrategy();
            MarkerService = new MarkerService(this);

            TextArea.TextEntered += TextEntered;
            TextArea.Caret.PositionChanged += CaretPositionChanged;
            TextArea.TextView.MouseHover += TextViewMouseHover;
            TextArea.TextView.MouseHoverStopped += TextViewMouseHoverStopped;
            TextArea.TextView.VisualLinesChanged += TextViewVisualLinesChanged;
            Document.TextChanged += Document_TextChanged;

            SetStyle();
            
        }

        private bool _modified;

        public bool Modified
        {
            get { return _modified; }
            set
            {
                if (_modified == value)
                    return;
                if (Module != null && Module.Document != null)
                {
                    Module.Document.Title = Module.Name;
                    if (_modified = value)
                        Module.Document.Title += "*";
                }
            }

        }

        private void Document_TextChanged(object sender, EventArgs e)
        {
            Modified = true;
        }



        private ToolTip toolTip;

        private void TextViewMouseHover(object sender, MouseEventArgs e)
        {
            var pos = TextArea.TextView.GetPositionFloor(e.GetPosition(TextArea.TextView) + TextArea.TextView.ScrollOffset);
            bool inDocument = pos.HasValue;
            if (inDocument)
            {
                TextLocation logicalPosition = pos.Value.Location;
                int offset = Document.GetOffset(logicalPosition);

                var markersAtOffset = MarkerService.GetMarkersAtOffset(offset);
                TextMarker markerWithToolTip = markersAtOffset.FirstOrDefault(marker => marker.ToolTip != null);

                if (markerWithToolTip != null)
                {
                    if (toolTip == null)
                    {
                        toolTip =
                            new ToolTip
                            {

                                PlacementTarget = this,
                                Background = Background,
                                Foreground = Foreground,
                                IsOpen = true,
                                Content = new TextBlock
                                {
                                    Text = markerWithToolTip.ToolTip,
                                    TextWrapping = TextWrapping.Wrap,
                                    Background = Background,
                                    Foreground = Foreground
                                },
                            };
                        toolTip.Closed += ToolTipClosed;
                        e.Handled = true;
                    }
                }
            }
        }

        void ToolTipClosed(object sender, RoutedEventArgs e)
        {
            toolTip = null;
        }

        void TextViewMouseHoverStopped(object sender, MouseEventArgs e)
        {
            if (toolTip != null)
            {
                toolTip.IsOpen = false;
                e.Handled = true;
            }
        }

        private void TextViewVisualLinesChanged(object sender, EventArgs e)
        {
            if (toolTip != null)
            {
                toolTip.IsOpen = false;
            }
        }

        private bool IsLetter(char c)
        {
            return char.IsLetterOrDigit(c) || c == '_';
        }

        private void TextEntered(object sender, TextCompositionEventArgs e)
        {

            bool b = CloseCompletionWindow();
            dynamic obj;
            switch (e.Text)
            {
                case ".":
                    obj = GetObject(TextArea.Caret.Offset - 2);
                    if (obj != null)
                    {
                        IEnumerable<Types.TypeBase> lst = Application.KeywordList.GetProperties(obj);
                        if (lst != null && lst.Count() > 0)
                        {
                            ShowCompletionWindow(lst);
                        }
                    }
                    break;
                case "(":
                    obj = GetObject(TextArea.Caret.Offset - 2);
                    if (obj != null && obj.IsCallable)
                        ShowInsightWindow(obj);
                    break;
                default:
                    if (b && e.Text.Length == 1 && IsLetter(e.Text[0]))
                    {
                        var line = TextArea.Document.GetLineByNumber(TextArea.Caret.Line);
                        var offset = TextArea.Caret.Offset;
                        var n = offset - line.Offset - 1;

                        if (n == 0 || !IsLetter(Text[offset - 2]))
                        {
                            _cw?.Close();
                            ShowCompletionWindow(Application.KeywordList);
                            _cw.StartOffset = offset - 1;
                            _cw.CompletionList.SelectItem(e.Text);
                            CloseCompletionWindow();
                        }
                    }
                    break;
            }

        }

        private void CaretPositionChanged(object sender, EventArgs e)
        {
            if (_iw != null && CaretOffset > _iw.StartOffset)
            {
                int n = 0;
                for (int i = _iw.StartOffset; i < CaretOffset; i++)
                {
                    if (Document.Text[i] == '(')
                        n++;
                    else if (Document.Text[i] == ')')
                        n--;
                }
                if (n != 0)
                    _iw.Close();
            }
        }

        private Types.Valuetype GetObject(int curr)
        {
            if (curr < 0)
                return null;
            Stack<string> names = new Stack<string>();
            string name = "";

            while (curr >= 0)
            {
                char c = Text[curr--];
                if (IsLetter(c))
                {
                    name = c + name;
                }
                else if (c == '.')
                {
                    if (name != "")
                    {
                        names.Push(name);
                        name = "";
                    }
                }
                else
                {
                    break;
                }
            }

            return Application.KeywordList.GetObject(name, names);

        }

        private void ShowInsightWindow(Types.Valuetype obj)
        {
            _iw = new OverloadInsightWindow(TextArea);
            _iw.Background = Background;
            _iw.Foreground = Foreground;
            _iw.Provider = new OverloadProvider(obj);
            _iw.Closed += (s, ev) => _iw = null;
            _iw.Show();
        }

        private bool CloseCompletionWindow()
        {

            if (_cw == null)
            {
                return true;
            }
            else
            {
                if (_cw.CompletionList.SelectedItem == null)
                {
                    _cw.Close();
                    return true;
                }
            }
            return false;
        }

        private void ShowCompletionWindow(IEnumerable<ICompletionData> lst)
        {
            // open code completion after the user has pressed dot:
            _cw = new CompletionWindow(TextArea);
            // provide AvalonEdit with the data:
            _cw.CompletionList.AddData(lst.OrderBy(d => d.Text));

            _cw.SetColors(Background, Foreground);
            _cw.FontSize = 12;
            _cw.CloseWhenCaretAtBeginning = true;
            _cw.Closed += (s, ev) => _cw = null;
            _cw.Width *= 2;
            _cw.Show();
        }
        
        public void SetStyle()
        {
            if (Cstyle == null)
            {
                Background = Brushes.Black;
                Foreground = Brushes.White;
                FontFamily = new FontFamily("Consolas");
                FontSize = 14;
            }
            else
            {
                Background = Cstyle.Background;
                Foreground = Cstyle.Foreground;
                SetValue(FoldingMargin.FoldingMarkerBrushProperty, Cstyle.Folding ?? Brushes.Gray);
                SetValue(FoldingMargin.FoldingMarkerBackgroundBrushProperty, Background);
                SetValue(FoldingMargin.SelectedFoldingMarkerBrushProperty, Cstyle.SelectedFolding ?? Brushes.Wheat);
                SetValue(FoldingMargin.SelectedFoldingMarkerBackgroundBrushProperty, Background);
                FontFamily = new FontFamily(Cstyle.Font[0]);
                FontSize = Cstyle.Font[1];
            }
        }

    }
}
